﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace FscNET.Apm
{
    public sealed class SyncGate
    {
        // Fields
        private int m_numReaders;
        private readonly Queue<SyncGateAsyncResult> m_qReadRequests;
        private readonly Queue<SyncGateAsyncResult> m_qWriteRequests;
        private SyncGateStates m_state;
        private readonly object m_syncLock;

        // Methods
        public SyncGate()
            : this(false)
        {
        }

        public SyncGate(bool blockReadersUntilFirstWriteCompletes)
        {
            m_syncLock = new object();
            m_qWriteRequests = new Queue<SyncGateAsyncResult>();
            m_qReadRequests = new Queue<SyncGateAsyncResult>();
            m_state = blockReadersUntilFirstWriteCompletes ? SyncGateStates.ReservedForWriter : SyncGateStates.Free;
        }

        public void BeginRegion(SyncGateMode mode, AsyncCallback asyncCallback)
        {
            BeginRegion(mode, asyncCallback, null);
        }

        public void BeginRegion(SyncGateMode mode, AsyncCallback asyncCallback, object asyncState)
        {
            if (asyncCallback == null)
            {
                throw new ArgumentNullException("asyncCallback");
            }
            SyncGateAsyncResult item = new SyncGateAsyncResult(mode, asyncCallback, asyncState);
            bool flag = false;
            Monitor.Enter(m_syncLock);
            switch (mode)
            {
                case SyncGateMode.Exclusive:
                    switch (m_state)
                    {
                        case SyncGateStates.Free:
                        case SyncGateStates.ReservedForWriter:
                            m_state = SyncGateStates.OwnedByWriter;
                            flag = true;
                            break;

                        case SyncGateStates.OwnedByReaders:
                        case SyncGateStates.OwnedByReadersAndWriterPending:
                            m_state = SyncGateStates.OwnedByReadersAndWriterPending;
                            m_qWriteRequests.Enqueue(item);
                            break;

                        case SyncGateStates.OwnedByWriter:
                            m_qWriteRequests.Enqueue(item);
                            break;
                    }
                    break;

                case SyncGateMode.Shared:
                    switch (m_state)
                    {
                        case SyncGateStates.Free:
                        case SyncGateStates.OwnedByReaders:
                            m_state = SyncGateStates.OwnedByReaders;
                            m_numReaders++;
                            flag = true;
                            break;

                        case SyncGateStates.OwnedByReadersAndWriterPending:
                        case SyncGateStates.OwnedByWriter:
                        case SyncGateStates.ReservedForWriter:
                            m_qReadRequests.Enqueue(item);
                            break;
                    }
                    break;
            }
            Monitor.Exit(m_syncLock);
            if (flag)
            {
                item.SetAsCompleted(null, true);
            }
        }

        public void EndRegion(IAsyncResult result)
        {
            if (result == null)
            {
                throw new ArgumentNullException("result");
            }
            SyncGateAsyncResult result2 = (SyncGateAsyncResult)result;
            result2.EndInvoke();
            Monitor.Enter(m_syncLock);
            if ((result2.Mode == SyncGateMode.Shared) && (--m_numReaders > 0))
            {
                Monitor.Exit(m_syncLock);
            }
            else
            {
                if (m_qWriteRequests.Count > 0)
                {
                    m_state = SyncGateStates.OwnedByWriter;
                    QueueCallback(m_qWriteRequests.Dequeue());
                }
                else if (m_qReadRequests.Count > 0)
                {
                    m_state = SyncGateStates.OwnedByReaders;
                    m_numReaders = m_qReadRequests.Count;
                    while (m_qReadRequests.Count > 0)
                    {
                        QueueCallback(m_qReadRequests.Dequeue());
                    }
                }
                else
                {
                    m_state = SyncGateStates.Free;
                }
                Monitor.Exit(m_syncLock);
            }
        }

        private static void InvokeCallback(object o)
        {
            ((SyncGateAsyncResult)o).SetAsCompleted(null, false);
        }

        private static void QueueCallback(SyncGateAsyncResult sgar)
        {
            ThreadPool.QueueUserWorkItem(InvokeCallback, sgar);
        }

        // Nested Types
        private sealed class SyncGateAsyncResult : AsyncResult
        {
            // Fields
            private readonly SyncGateMode m_mode;

            // Methods
            internal SyncGateAsyncResult(SyncGateMode mode, AsyncCallback asyncCallback, object state)
                : base(asyncCallback, state)
            {
                m_mode = mode;
            }

            // Properties
            internal SyncGateMode Mode
            {
                get
                {
                    return m_mode;
                }
            }
        }

        private enum SyncGateStates
        {
            Free,
            OwnedByReaders,
            OwnedByReadersAndWriterPending,
            OwnedByWriter,
            ReservedForWriter
        }
    }


}
